// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md

#include "bsp/torch/torch.hpp"
#include "AL3644TT.hpp"

#include "board/BoardDefinitions.hpp"
#include "drivers/i2c/DriverI2C.hpp"

#include "fsl_common.h"
#include <log/log.hpp>

using namespace drivers;

namespace bsp::torch
{
    namespace
    {
        std::shared_ptr<drivers::DriverI2C> i2c;
        constexpr auto powerStateTransitionDelayMs = 5;

        I2CAddress addr = {.deviceAddress = 0x63, .subAddress = 0, .subAddressSize = 1};

        std::shared_ptr<DriverGPIO> gpio;
        constexpr unsigned short max_current_mA = 150;
        ColourTemperature currentColourTemp     = ColourTemperature::Warmest;

        void powerOnTorch()
        {
            gpio->WritePin(static_cast<std::uint32_t>(BoardDefinitions::TORCH_DRIVER_EN), AL3644TT_ENABLE);
        }

        void powerOffTorch()
        {
            gpio->WritePin(static_cast<std::uint32_t>(BoardDefinitions::TORCH_DRIVER_EN), AL3644TT_DISABLE);
        }

        void enablePowerSupply()
        {
            // Make sure torch power is supplied to write or read i2c message and wait a bit
            if (getState() == State::Off) {
                powerOnTorch();
                vTaskDelay(pdMS_TO_TICKS(powerStateTransitionDelayMs));
            }
        }

        bool write(const drivers::I2CAddress &addr, const uint8_t *txBuff, const size_t size)
        {
            enablePowerSupply();
            auto writeBytes = i2c->Write(addr, txBuff, size);
            return writeBytes == size;
        }

        bool read(const drivers::I2CAddress &addr, uint8_t *rxBuff, const size_t size)
        {
            enablePowerSupply();
            auto readBytes = i2c->Read(addr, rxBuff, size);
            return readBytes == size;
        }
    } // namespace

    std::int32_t init()
    {
        i2c = DriverI2C::Create(
            static_cast<I2CInstances>(BoardDefinitions::TORCH_DRIVER_I2C),
            DriverI2CParams{.baudrate = static_cast<std::uint32_t>(BoardDefinitions::TORCH_DRIVER_I2C_BAUDRATE)});

        gpio = DriverGPIO::Create(static_cast<GPIOInstances>(BoardDefinitions::TORCH_DRIVER_GPIO), DriverGPIOParams{});

        // OUTPUT
        gpio->ConfPin(DriverGPIOPinParams{.dir      = DriverGPIOPinParams::Direction::Output,
                                          .irqMode  = DriverGPIOPinParams::InterruptMode::NoIntmode,
                                          .defLogic = 0,
                                          .pin      = static_cast<std::uint32_t>(BoardDefinitions::TORCH_DRIVER_EN)});
        powerOffTorch();
        vTaskDelay(pdMS_TO_TICKS(powerStateTransitionDelayMs));
        powerOnTorch();
        vTaskDelay(pdMS_TO_TICKS(powerStateTransitionDelayMs));

        auto present = isPresent();
        turn(State::Off);

        return present ? kStatus_Success : kStatus_Fail;
    }

    void deinit()
    {
        turn(State::Off);

        /* Explicitly release peripherals */
        i2c.reset();
        gpio.reset();
    }

    bool isPresent()
    {
        al3644tt_device_id_reg id;
        addr.subAddress = AL3644TT_DEVICE_ID_REG;
        auto status     = read(addr, reinterpret_cast<std::uint8_t *>(&id), sizeof(al3644tt_device_id_reg));

        if (!status) {
            return false;
        }

        if (id.device_id == AL3644TT_ID && (id.silicon_rev == AL3644TT_REV_1 || id.silicon_rev == AL3644TT_REV_2)) {
            return true;
        }
        LOG_WARN("Something is present at the torch LED driver address (0x%lx), but not the AL3644TT",
                 addr.deviceAddress);
        return false;
    }

    bool setCurrent(const unsigned short mA)
    {
        // set the same current for both channels
        addr.subAddress = AL3644TT_LED1_TORCH_BRIGHTNESS_REG;
        al3644tt_led1_torch_brightness_reg led1_brightness{
            .brightness_code          = al3644tt_current_convert(mA > max_current_mA ? max_current_mA : mA),
            .led2_brightness_override = AL3644TT_LED1_TORCH_BRIGHTNESS_OVERRIDE,
        };
        return write(
            addr, reinterpret_cast<std::uint8_t *>(&led1_brightness), sizeof(al3644tt_led1_torch_brightness_reg));
    }

    bool turn(State state, ColourTemperature colourTemp)
    {
        if (state == State::On) {
            powerOnTorch();
            setCurrent(max_current_mA);
        }

        if (colourTemp != ColourTemperature::NoChange) {
            currentColourTemp = colourTemp;
        }

        addr.subAddress = AL3644TT_ENABLE_REG;
        al3644tt_enable_reg en_reg{
            .led1_en = static_cast<std::uint8_t>(currentColourTemp == ColourTemperature::Warmest && state == State::On
                                                     ? AL3644TT_LED_ENABLED
                                                     : AL3644TT_LED_DISABLED),
            .led2_en = static_cast<std::uint8_t>(currentColourTemp == ColourTemperature::Coldest && state == State::On
                                                     ? AL3644TT_LED_ENABLED
                                                     : AL3644TT_LED_DISABLED),
            .mode    = 0b10,
            .torch_temp_en = 0,
            .strobe_en     = 0,
            .strobe_type   = 0,
            .tx_pin_en     = 0,
        };

        auto status = write(addr, reinterpret_cast<std::uint8_t *>(&en_reg), sizeof(al3644tt_enable_reg));

        if (state == State::Off) {
            powerOffTorch();
        }

        return status;
    }

    State getState()
    {
        auto read = gpio->ReadPin(static_cast<std::uint32_t>(BoardDefinitions::TORCH_DRIVER_EN));
        return read ? State::On : State::Off;
    }

    ColourTemperature getColorTemp()
    {
        return currentColourTemp;
    }

    bool toggle()
    {
        auto state = getState();
        return turn(state == State::Off ? State::On : State::Off);
    }
} // namespace bsp::torch
